Skip to content

Add instances() API for primitive instancing#8952

Open
aashu2006 wants to merge 6 commits into
processing:mainfrom
aashu2006:instances-api
Open

Add instances() API for primitive instancing#8952
aashu2006 wants to merge 6 commits into
processing:mainfrom
aashu2006:instances-api

Conversation

@aashu2006

@aashu2006 aashu2006 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Addresses #8911

Changes

  • Add a new instances(count) API for primitive instancing in WebGL.
  • Support instancing for primitives including sphere(), box(), plane(), ellipsoid(), cylinder(), cone(), and torus().
  • Preserve existing model(geometry, count) behavior while supporting instances(count).model(geometry)
  • Forward instance counts through the existing retained-geometry rendering pipeline.
  • Add TypeScript support for the new InstancesWrapper API.
  • Add unit tests for primitive/model instancing and explicit count precedence.
  • Add WebGL and WebGPU visual tests with reference snapshots.
  • Update the documentation example to use p5.strands and instanceIndex().

PR Checklist

  • npm run lint passes
  • Inline reference is included / updated
  • Unit tests are included / updated

@aashu2006 aashu2006 changed the title Instances api Add instances() API for primitive instancing Jun 22, 2026
@p5-bot

p5-bot Bot commented Jun 22, 2026

Copy link
Copy Markdown

Comment thread src/core/p5.Renderer3D.js Outdated
model(model, count = 1) {
// Use _instanceCount only when count was NOT explicitly passed.
// arguments.length distinguishes model(geom) from model(geom, 1).
if (arguments.length < 2 && this._instanceCount !== undefined) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a tad unintuitive seeing a default value for count and having this condition -- at least, it's not obvious whether or not it's included. Thoughts on leaving out the default value for count, and then we can do something like:

count = count ?? this._instanceCount ?? 1
``

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this to use count = count ?? this._instanceCount ?? 1; instead.

Comment thread src/webgl/3d_primitives.js Outdated
* @method instances
* @param {Number} count number of instances to draw. Must be a positive
* integer.
* @returns {Object} an object with methods `sphere`, `box`, `plane`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For TypeScript, if we just call it Object then all methods chained onto it will look like type errors. Take a look at the types added for the different p5.strands hooks in #8644, they add typedefs that add the different methods

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did follow the pattern from #8644 and updated the typedefs, this also required a small fix in the type generator so the optional parameters are generated correctly.

Comment thread src/webgl/3d_primitives.js Outdated
this._assert3d('instances');

if (typeof count !== 'number' || !isFinite(count) || count < 1) {
console.log(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be an FES message so that it's disabled when FES is disabled

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated this to use the Friendly Error System.

Comment thread src/webgl/3d_primitives.js Outdated
'🌸 p5.js says: instances() requires a positive integer count.' +
' Clamping to 1.'
);
count = Math.max(1, Math.round(count) || 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there an input where we don't just set it directly to 1 here? might be more robust to do that

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

simplified this to clamp directly to 1.

Comment thread src/webgl/3d_primitives.js Outdated
} finally {
r._instanceCount = undefined;
}
return p;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the intention to be able to chain multiple things? If so I think this return value would need to be the returned object below instead of p. If not, we probably don't need to return p (or track p at all)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it a terminal draw call and removed the return value to avoid that misleading chaining behavior.

Comment thread src/webgl/3d_primitives.js Outdated
* }
* function draw() {
* background(200);
* instances(10).sphere(20);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These currently all draw on top of each other since now shader is present, can we add a p5.strands shader to make use of the instances?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated the example to use p5.strands and instanceIndex so the instancing behavior is visible.

Comment thread src/webgl/3d_primitives.js Outdated
* <div>
* <code>
* // Draw 10 spheres in a single instanced draw call.
* // A custom shader reads gl_InstanceID to offset each sphere.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably use strands and not mention GLSL-specific things

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated the wording to use the strands API.

@davepagurek davepagurek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking good! I left some comments. Once the example in the docs is more complete, could we add it as a visual test in both webgl and webgpu modes to have an end-to-end test for each?

@aashu2006

Copy link
Copy Markdown
Contributor Author

@davepagurek Done! I used the updated example as the basis and added the corresponding visual tests for both WebGL and WebGPU.

@davepagurek davepagurek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates! Some thoughts:

  • Should the parameters be recursively passed into convertTypeToTypeScript to handle all the same cases there? (Does doing that break anything?)
  • For the visual tests to work, you'll need to run it first locally and commit the added snapshots so that CI has something to compare to in the future.
  • There are some other shape drawing methods that aren't added yet, such as plane, line, etc. Maybe worth looking through the 3d_primitives file and seeing if anything else there should be added?

@aashu2006

Copy link
Copy Markdown
Contributor Author

thanks for the quick feedback @davepagurek

I pushed another update, also added the visual test snapshots this time, my bad for missing them in the previous push.

I looked into the convertTypeToTypeScript() suggestion as well. It already handles OptionalType recursively, but convertFunctionTypeForInterface() still needs the parameter name and optionality first, so I kept the unwrapping there before passing the type through convertTypeToTypeScript().

I also took another pass through 3d_primitives.js. The omitted ones like line() use the immediate-mode path, so they don't make use of _instanceCount. I did notice a few retained-geometry shapes like triangle(), rect(), quad(), and ellipse()/arc() could technically be supported too. Would you want instances() to cover those as well, or keep it focused on the current set?

@davepagurek

Copy link
Copy Markdown
Contributor

Sounds good, thanks for looking into the typescript stuff!

Yeah, let's see if we can support all the shapes if we can.

@aashu2006

Copy link
Copy Markdown
Contributor Author

Thanks, I updated the remaining retained-geometry shapes as well. I think that should cover everything from my side now, but happy to make any further changes if needed 🙂

@davepagurek davepagurek left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the updates! I think we can support line and other APIs that go through begin/endShape by updating the count parameter in endShape to do what you've done for model.

As a list thing, I think we use instanceID() in some examples. If you search for instanceID() in the codebase to find those, can we update those to use your new syntax and avoid buildGeometry if they're just drawing a sphere or something to simplify them?

@aashu2006

Copy link
Copy Markdown
Contributor Author

I added support for immediate mode APIs by forwarding instance count through endShape(), so line(), point(), bezier(), and spline() now work with instances(). I also updated the examples to use instanceIndex and instances(), and added visual tests for both WebGL and WebGPU.

For curve(), I kept it conditional since spline() is the standard API in 2.x, while curve() is only available in compatibility setups.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants